export function patch(oldVnode, vnode) {
  if (!oldVnode) {
    return createElm(vnode);
  }
  if (oldVnode.nodeType === 1) {
    // 用vnode来生成真实dom 替换原来的dom元素
    // 节点父亲
    const parentElm = oldVnode.parentNode;

    // 根据虚拟节点 创建元素
    let elm = createElm(vnode);
    // 第一次渲染后，是删除掉节点。 下一次使用无法获取
    parentElm.insertBefore(elm, oldVnode.nextSibling);

    parentElm.removeChild(oldVnode);
    return elm;
  } else {
    // 新旧对比 都是虚拟vdom
    // console.log(oldVnode, vnode);
    // 如果标签名不一样，直接删除老的换成新的即可
    if (oldVnode.tag !== vnode.tag) {
      // console.log(oldVnode);
      // 可以通过vnode.el属性获取真实dom元素
      oldVnode.el.parentNode.replaceChild(createElm(vnode), oldVnode.el);
    }
    // 表示当前新节点 复用旧的节点
    let el = (vnode.el = oldVnode.el);
    // 如果两个虚拟节点是本文 节点时 比较文本节点
    if (vnode.tag === undefined) {
      if (oldVnode.text !== vnode.text) {
        el.textContent = vnode.text;
      }
      return;
    }
    // 如果标签一样 比较属性
    patchProps(vnode, oldVnode.data);

    // 一方有儿子 一方没有儿子
    let oldChildren = oldVnode.children || [];
    let newChildren = vnode.children || [];

    if (oldChildren.length > 0 && newChildren.length > 0) {
      // 双方都有儿子
      // Vue用了双指针方法比较
      pathChildren(el, oldChildren, newChildren);
    } else if (newChildren.length) {
      // 老的没有儿子， 新的有儿子
      // 把新的创建新的节点，插入vnode.el里面
      for (let i = 0; i < newChildren.length; i++) {
        let child = createElm(newChildren[i]);
        el.appendChild(child);
      }
    } else if (oldChildren.length) {
      // 老得有儿子 新的没有儿子
      // 把儿子情况
      el.innerHTML = "";
    } else {
      // 都没有儿子、
      // 不处理
    }
    return el
  }
}

function isSameVnode(oldVnode, newVnode) {
  return oldVnode.tag === newVnode.tag && oldVnode.key === newVnode.key;
}

function pathChildren(el, oldChildren, newChildren) {
  let oldStartIndex = 0;
  let oldStartVnode = oldChildren[0];
  let oldEndIndex = oldChildren.length - 1;
  let oldEndVnode = oldChildren[oldEndIndex];

  let newStartIndex = 0;
  let newStartVnode = newChildren[0];
  let newEndIndex = newChildren.length - 1;
  let newEndVnode = newChildren[newEndIndex];

  const makeIndexByKey = (children) => {
    return children.reduce((memo, current, index) => {
      if (current.key) {
        memo[current.key] = index
      }
      return memo
    }, {})
  }

  const keysMap = makeIndexByKey(oldChildren)
  // 开始比较
  // 头头对比 尾尾对比
  while (oldStartIndex <= oldEndIndex && newStartIndex <= newEndIndex) {
    if (!oldStartVnode) {
      // 已经被移动走了
      oldStartVnode = oldChildren[++oldStartIndex]
    } else if (!oldEndVnode) {
      oldEndVnode = oldChildren[--oldEndIndex]
    }
    // 同时循环新的节点和老的节点， 有一方循环完毕就结束
    if (isSameVnode(oldStartVnode, newStartVnode)) {
      // 相同标签 ，比较属性，比较儿子
      patch(oldStartVnode, newStartVnode);
      oldStartVnode = oldChildren[++oldStartIndex];
      newStartVnode = newChildren[++newStartIndex];
    } else if (isSameVnode(oldEndVnode, newEndVnode)) {
      patch(oldEndVnode, newEndVnode);
      oldEndVnode = oldChildren[--oldEndIndex];
      newEndVnode = newChildren[--newEndIndex];
    } else if (isSameVnode(oldStartVnode, newEndVnode)) {
      patch(oldStartVnode, newEndVnode)
      el.insertBefore(oldStartVnode.el, oldEndVnode.el.nextSibling)
      oldStartVnode = oldChildren[++oldStartIndex]
      newEndVnode = newChildren[--newEndIndex]
    } else if (isSameVnode(oldEndVnode, newStartVnode)) {
      patch(oldEndVnode, newStartVnode)
      el.insertBefore(oldEndVnode.el, oldStartVnode.el)
      oldEndVnode = oldChildren[--oldEndIndex]
      newStartVnode = newChildren[++newStartIndex]
    } else {
      // 乱序比较 核心diff
      // 需要根据key和对应的索引将老的内容生成映射表
      // 遍历新的，跟旧的对比。
      // 移动的时候，由于数组塌陷问题。补null 最后遍历如果是null再清除

      // 用新的去老的中查找
      let moveIndex = keysMap[newStartVnode.key]
      if (moveIndex === undefined) {
        // 如果没有找到，说明旧的数组中是不存在。这个是新加入的数据， 直接插入到老的前面
        el.insertBefore(createElm(newStartVnode), oldStartVnode.el)
      } else {
        // 找到，说明可以复用
        let moveNode = oldChildren[moveIndex]
        // 此节点已经被移动走了
        oldChildren[moveIndex] = null
        // 将可以复用的节点移动到新的位置
        // TODO为什么插入的对比元素都是旧的？因为复用，所有的元素都是拿旧的位置在移动
        el.insertBefore(moveNode.el, oldStartVnode.el)
        // 移动完成后，需要对其节点进行diff
        patch(moveNode, newStartVnode)

      }
      newStartVnode = newChildren[++newStartIndex]
    }
  }
  // 如果用户追加了一个怎么办
  if (newStartIndex <= newEndIndex) {
    for (let i = newStartIndex; i <= newEndIndex; i++) {
      // 这个只能在后面追加，如果是从后面我往前面走。应该要往前面插
      // el.appendChild(createElm(newChildren[i]));
      // 看一下尾指针的下一个元素是否存在
      let anchor =
        newChildren[newEndIndex + 1] === undefined
          ? undefined
          : newChildren[newEndIndex + 1].el;
      // insertBefore 如果第二个参数为空 则为appendChild
      el.insertBefore(createElm(newChildren[i]), anchor);
    }
  }
  // 旧的多余的节点，删除
  if (oldStartIndex <= oldEndIndex) {
    for (let i = oldStartIndex; i <= oldEndIndex; i++) {
      // 如果老的多 将老节点删除，但是可能有null的清空
      if (oldChildren[i]) {
        el.removeChild(oldChildren[i].el)
      }
    }
  }
}

// 初次渲染时可以调用此方法，后续更新也可以调用，比对属性更新
function patchProps(vnode, oldProps = {}) {
  let newProps = vnode.data || {};
  let el = vnode.el;
  // 删除旧的
  let newStyle = newProps.style || {};
  let oldStyle = oldProps.style || {};
  for (let key in oldStyle) {
    if (!newStyle[key]) {
      el.style[key] = "";
    }
  }
  for (let key in oldProps) {
    if (!newProps[key]) {
      el.removeAttribute(key);
    }
  }
  for (let key in newProps) {
    if (key === "style") {
      for (let styleName in newProps.style) {
        el.style[styleName] = newProps.style[styleName];
      }
    } else {
      el.setAttribute(key, newProps[key]);
    }
  }
}

function createComponent(vnode) {
  let i = vnode.data;
  if ((i = i.hook) && (i = i.init)) {
    i(vnode); // 调用init方法
  }
  if (vnode.componentInstance) {
    return true;
  }
}

export function createElm(vnode) {
  let { tag, data, children, text, vm } = vnode;
  if (typeof tag === "string") {
    if (createComponent(vnode)) {
      return vnode.componentInstance.$el;
    }
    // 虚拟节点会有个el属性对应真实节点
    vnode.el = document.createElement(tag);
    patchProps(vnode);
    (children || []).forEach((child) => {
      vnode.el.appendChild(createElm(child));
    });
  } else {
    vnode.el = document.createTextNode(text);
  }
  return vnode.el;
}
